using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace IUPUI.StellarResults.Client.UIElements
{
	/// <summary>
	/// The view of a telescope in an <see cref="Observatory"/>.
	/// </summary>
	public partial class TelescopeControl : UserControl
	{
		#region Private Properties
		private Simulation m_Simulation;
		private Observatory m_Observatory;
		private bool m_ShowGrid = true;
		private int m_GridDivisions = 10;
		private DateTime m_CurrentDateTimeUTC;
		private Dictionary<AstronomicalObject, Rectangle> m_ObjectLocations =
			new Dictionary<AstronomicalObject, Rectangle>();
		private Rectangle m_HorizonLocation = new Rectangle();

		private string m_ToolTipText = null;
		#endregion Private Properties

		#region Public Properties
		/// <summary>
		/// Gets or sets the current date time.
		/// </summary>
		/// <value>The current date time.</value>
		public DateTime CurrentDateTimeUTC
		{
			get
			{
				return m_CurrentDateTimeUTC;
			}
			set
			{
				if (m_CurrentDateTimeUTC != value)
				{
					m_CurrentDateTimeUTC = value;
					OnCurrentDateTimeChanged();
				}
			}
		}

		/// <summary>
		/// Gets or sets the simulation.
		/// </summary>
		/// <value>The simulation.</value>
		[Browsable(false)]
		public Simulation Simulation
		{
			get
			{
				return m_Simulation;
			}
			set
			{
				if (m_Simulation != value)
				{
					m_Simulation = value;
					OnSimulationChanged();
				}
			}
		}

		/// <summary>
		/// Gets or sets the observatory.
		/// </summary>
		/// <value>The observatory.</value>
		public Observatory Observatory
		{
			get
			{
				return m_Observatory;
			}
			set
			{
				if (m_Observatory != value)
				{
					m_Observatory = value;
					OnObservatoryChanged();
				}
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether [show grid].
		/// </summary>
		/// <value><c>true</c> if [show grid]; otherwise, <c>false</c>.</value>
		public bool ShowGrid
		{
			get
			{
				return m_ShowGrid;
			}
			set
			{
				if (m_ShowGrid != value)
				{
					m_ShowGrid = value;

					// Force refresh of the view
					this.Refresh();
				}
			}
		}

		/// <summary>
		/// Gets or sets the grid divisions.
		/// </summary>
		/// <value>The grid divisions.</value>
		public int GridDivisions
		{
			get
			{
				return m_GridDivisions;
			}
			set
			{
				if (m_GridDivisions != value)
				{
					m_GridDivisions = value;

					// Force refresh of the view
					if (m_ShowGrid)
					{
						this.Refresh();
					}
				}
			}
		}
		#endregion Public Properties

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the <see cref="TelescopeControl"/> class.
		/// </summary>
		public TelescopeControl()
		{
			InitializeComponent();

			// The control must be redrawn when resized
			this.ResizeRedraw = true;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="TelescopeControl"/> class.
		/// </summary>
		public TelescopeControl(
			IContainer container)
			: this()
		{
			container.Add(this);
		}
		#endregion Constructors

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.Paint"></see> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.Windows.Forms.PaintEventArgs"></see> that contains the event data.</param>
		protected override void OnPaint(PaintEventArgs e)
		{
			// Initialize
			Graphics g = e.Graphics;
			g.SmoothingMode = SmoothingMode.AntiAlias;
			Pen blackPen = new Pen(Color.Black, 1);
			Pen gridPen = new Pen(Color.LightGray, 1);

			// Determine size, this is a circle so use smallest size
			int size = (this.Width < this.Height) ? this.Width : this.Height;
			Rectangle rectangle = new Rectangle(1, 1, size - 3, size - 3);

			LinearGradientBrush gradientBrush =
				new LinearGradientBrush(rectangle, Color.White, Color.AliceBlue, 30);

			// Draw the telescope lens
			g.FillEllipse(gradientBrush, 4, 4, size - 9, size - 9);

			// Draw grid
			if (m_ShowGrid)
			{
				g.DrawRectangles(
					gridPen, 
					Drawing.DrawingUtilities.GetGridPoints(
						size, 
						m_GridDivisions));
			}

			// Draw each tracked object
			if (m_Simulation != null)
			{
				CheckIfNeedToMoveObservatory();
				m_ObjectLocations.Clear();
				foreach (AstronomicalObject astronomicalObject in m_Simulation.AstronomicalObjects)
				{
					DrawAstronomicalObject(g, astronomicalObject, size);
				}
			}

			// Draw horizon if necessary
			if (m_Observatory != null 
				&& ((double)m_Observatory.ViewAngle - m_Observatory.Altazimuth.Altitude) > 0)
			{
				// Compute horizon line
				int line =
					size/2 - ((int)(size / 2 * Math.Sin(m_Observatory.Altazimuth.Altitude*Math.PI/180) / Math.Sin((double)m_Observatory.ViewAngle)*Math.PI/180));

				// Draw horizon
				m_HorizonLocation = new Rectangle(0, size - line, size, size);
				g.FillRectangle(
					Brushes.DarkGreen,
					m_HorizonLocation);
			}
			else
			{
				m_HorizonLocation = new Rectangle();
			}

			// Draw rim around lens
			GraphicsPath path = new GraphicsPath();
			path.AddEllipse(4, 4, size - 9, size - 9);
			g.SetClip(path, CombineMode.Exclude);
			g.FillRectangle(Brushes.DarkGray, rectangle);
			g.SetClip(new Rectangle(0, 0, size, size));
			g.DrawEllipse(blackPen, rectangle);
			g.DrawEllipse(blackPen, 4, 4, size - 9, size - 9);

			// Set visible control size
			GraphicsPath region = new GraphicsPath();
			region.AddEllipse(0, 0, size - 1, size - 1);
			this.Region = new Region(region);

			// Cleanup
			blackPen.Dispose();
			gridPen.Dispose();
			gradientBrush.Dispose();

			base.OnPaint(e);
		}

		/// <summary>
		/// Checks if need to move observatory.
		/// </summary>
		private void CheckIfNeedToMoveObservatory()
		{
			if (!String.IsNullOrEmpty(m_Observatory.AstronomicalObjectBeingTracked))
			{
				// Find the object
				AstronomicalObject astronomicalObject =
					m_Simulation.AstronomicalObjects.Find(
						delegate(AstronomicalObject ao)
						{
							return ao.ID == m_Observatory.AstronomicalObjectBeingTracked;
						});

				if (astronomicalObject != null)
				{
					// If the object was found, get its altazimuth and position the observatory
					Altazimuth altazimuth = AstronomicalCalculations.ComputeAzimuthAngle(
						astronomicalObject,
						m_Observatory,
						m_Simulation.ObservationType,
						m_CurrentDateTimeUTC,
						m_Simulation.StartDateTime);

					// Need to make sure the altitude is > 0 before trying to move the telescope
					if (altazimuth.Altitude > 0)
					{
						m_Observatory.ChangePosition(altazimuth);
					}
				}
			}
		}

		/// <summary>
		/// Draws the astronomical object.
		/// </summary>
		/// <param name="g">The g.</param>
		/// <param name="astronomicalObject">The astronomical object.</param>
		/// <param name="size">The size.</param>
		private void DrawAstronomicalObject(
			Graphics g,
			AstronomicalObject astronomicalObject,
			int size)
		{
			Pen aoPen;
			Brush aoBrush;

			// Get brush and pen
			try
			{
				Color representationalColor =
					Color.FromName(astronomicalObject.RepresentationColor);
				aoBrush = new SolidBrush(representationalColor);
				aoPen = new Pen(representationalColor);
			}
			catch (Exception)
			{
				// If an invalid color was selected, then use the default pen
				aoPen = new Pen(Color.Black, 1);
				aoBrush = new SolidBrush(Color.Black);
			}

			Altazimuth altazimuth = AstronomicalCalculations.ComputeAzimuthAngle(
				astronomicalObject,
				m_Observatory,
				m_Simulation.ObservationType,
				m_CurrentDateTimeUTC,
				m_Simulation.StartDateTime);

			// Compute where object should be drawn on screen
			ViewOffset viewOffset = new ViewOffset();
			viewOffset.ComputeOffset(
				altazimuth,
				m_Observatory,
				size);

			// Draw object if it can be visible
			if (viewOffset.Visible)
			{
				Rectangle rectangle =
					new Rectangle(
					size / 2 + viewOffset.X - (astronomicalObject.DiameterInPixels / 2),
					size / 2 - viewOffset.Y - (astronomicalObject.DiameterInPixels / 2),
					astronomicalObject.DiameterInPixels,
					astronomicalObject.DiameterInPixels);

				g.FillEllipse(
					aoBrush,
					rectangle);

				// Add object location
				m_ObjectLocations.Add(astronomicalObject, rectangle);
			}

			if (aoPen != null)
			{
				aoPen.Dispose();
			}
			if (aoBrush != null)
			{
				aoBrush.Dispose();
			}
		}
		/// <summary>
		/// Called when [current date time changed].
		/// </summary>
		protected virtual void OnCurrentDateTimeChanged()
		{
			// Redraw control if there are astronomical objects being tracked
			this.Refresh();
		}

		/// <summary>
		/// Handles the Modified event of the m_TrackedAstronomicalObjects control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void m_TrackedAstronomicalObjects_Modified(object sender, EventArgs e)
		{
			// Redraw control if there are astronomical objects being tracked and the simulation isnt running
			if (!m_Simulation.IsSimulationRunning)
			{
				this.Refresh();
			}
		}

		/// <summary>
		/// Called when [simulation changed].
		/// </summary>
		private void OnSimulationChanged()
		{
			if (m_Simulation != null)
			{
				m_Simulation.AstronomicalObjects.Modified += 
					new EventHandler(m_TrackedAstronomicalObjects_Modified);
			}
		}

		/// <summary>
		/// Called when [observatory changed].
		/// </summary>
		protected virtual void OnObservatoryChanged()
		{
			m_Observatory.ObservatoryViewAngleChanged += 
				new EventHandler<ININ.EventArgs<double>>(m_Observatory_ObservatoryViewAngleChanged);

			m_Observatory.AstronomicalObjectBeingTrackedChanged += 
				new EventHandler<ININ.EventArgs<string>>(m_Observatory_AstronomicalObjectBeingTrackedChanged);
		}

		/// <summary>
		/// Handles the AstronomicalObjectBeingTrackedChanged event of the m_Observatory control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="ININ.EventArgs&lt;System.String&gt;"/> instance containing the event data.</param>
		private void m_Observatory_AstronomicalObjectBeingTrackedChanged(object sender, ININ.EventArgs<string> e)
		{
			// Redraw the image
			this.Refresh();
		}

		/// <summary>
		/// Handles the ObservatoryViewAngleChanged event of the m_Observatory control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="ININ.EventArgs&lt;System.Double&gt;"/> instance containing the event data.</param>
		private void m_Observatory_ObservatoryViewAngleChanged(object sender, ININ.EventArgs<double> e)
		{
			// Redraw the image since the view angle has changed
			this.Refresh();
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.MouseMove"></see> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"></see> that contains the event data.</param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);
			Point point = new Point(e.X, e.Y);

			// Check if the mouse if over the horizon
			if (m_HorizonLocation != null && m_HorizonLocation.Contains(point))
			{
				if (m_ToolTipText != Properties.Resources.HorizonName)
				{
					m_ToolTip.SetToolTip(this, Properties.Resources.HorizonName);
					m_ToolTipText = Properties.Resources.HorizonName;
				}
				return;
			}

			foreach (AstronomicalObject astronomicalObject in m_ObjectLocations.Keys)
			{
				// Check if the mouse is over this object, and if so, set the tooltip
				if (m_ObjectLocations[astronomicalObject].Contains(point))
				{
					if (m_ToolTipText != astronomicalObject.Name)
					{
						m_ToolTip.SetToolTip(this, astronomicalObject.Name);
						m_ToolTipText = astronomicalObject.Name;
					}
					return;
				}
			}

			m_ToolTip.SetToolTip(this, null);
			m_ToolTipText = null;
		}
	}
}
